/*
 * Copyright (c) 1999-2004 eVelopers Corporation. All rights reserved.
 *
 * This is open source software; you can use, redistribute and/or modify 
 * it under the terms of the Open Software Licence v 2.1 as published by the Open 
 * Source Initiative.
 *
 * You should have received a copy of the Open Software Licence along with this
 * application; if not, contact the Open Source Initiative (http://opensource.org).
 */
package com.unimod.fsml.validation;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.swt.widgets.Display;

import com.evelopers.common.exception.CommonException;
import com.unimod.fsml.model.FSMLModelHelper;
import com.unimod.fsml.util.CommonUtils;
import com.unimod.fsml.validation.problems.Problem;
import com.unimod.fsml.validation.problems.ProblemFactory;

class InternalValidator {
    private static final Log log = LogFactory.getLog(InternalValidator.class);
	
	private IFile file;
	
	/** maps IMarker to ProblemMarker */
	private Map<IMarker, Problem> marker2problem = new HashMap<IMarker, Problem>();
	
	/** maps ModelElement to Problems associated with it */
	private Map<ProblemToken, List<Problem>> token2problems = new HashMap<ProblemToken, List<Problem>>();
	
	/** set of model elements that had problems during previous 
	 * validation. We have to store them to notify if they
	 * do not have any more problems after current validation*/
	private Set<ProblemToken> oldTokensWithProblems;
	
    /** problems factory */
	private ProblemFactory factory;
	private Display display;
	private FSMLModelHelper modelHelper;
	
	InternalValidator(Display display, IFile file) {
		modelHelper = FSMLModelHelper.getInstance();
		this.file = file;
		this.display = display;
		factory = ProblemFactory.createProblemFactory();
	}

	void validate(IProgressMonitor monitor) {
		try {
			modelHelper.prepareValidation(CommonUtils.convertStreamToString(file.getContents()));
		} catch (CommonException e) {
			log.error("Cannot prepare for validation", e);
		} catch (CoreException e) {
			log.error("Cannot get input file contents", e);
		}
		unregisterAllProblems();
			
        for (Iterator<ProblemToken> iter = modelHelper.getCOMap().getLexer().getProblemTokenIterator(); iter.hasNext();) {
            if (monitor.isCanceled()) {
                return;
            }
    		
            wrongTokenType(iter.next());
        }
		// Notify unimod of old model elements and
 		// model elements with problems found during current validation.
		// Execute notification process in UI thread,
		// because notification will lead to UI figures updates
		display.asyncExec(new ProblemNotification());
	}

	public void wrongTokenType(ProblemToken problemToken) {
		Problem p = factory.createWrongTokenTypeProblem(createMarker(), problemToken);
		registerProblem(p);
	}
    
    /**
	 * Removes all markers from given unimod resource file
	 *
	 */
	void unregisterAllProblems() {
		try {
			if (file.exists()) {
				file.deleteMarkers(IMarker.PROBLEM, true, 10);
			}
			
			marker2problem.clear();
			// save old model elements with problems
			oldTokensWithProblems = new HashSet<ProblemToken>(token2problems.keySet());
			token2problems.clear();
			
		} catch (CoreException e) {
			e.printStackTrace();

			throw new RuntimeException("Unexpected error inside InternalValidator.deleteAllMarkers()", e);
		}
	}

	private IMarker createMarker() {
		try {
			return file.createMarker(IMarker.PROBLEM);
		} catch (CoreException e) {
			e.printStackTrace();
			
			throw new RuntimeException("Unexpected error inside InternalValidator.createMarker()", e);
		}
	}
	
	private void registerProblem(Problem p) {
		if (p == null) {
			return;
		}
		
		marker2problem.put(p.getMarker(), p);
		
		addTokenProblem(p);
	}
	
	private void addTokenProblem(Problem p) {
		ProblemToken[] mes = p.getProblemTokens();
		for (int i = 0; i < mes.length; i++) {
			List<Problem> l = token2problems.get(mes[i]);
			
			if (l == null) {
				l = new LinkedList<Problem>();
				token2problems.put(mes[i], l);
			}
			
			l.add(p);
		}
		
	}
	
	Problem getProblem(IMarker marker) {
		return marker2problem.get(marker);
	}
	
	public Problem[] getProblems(ProblemToken problemToken) {
		List<Problem> p = token2problems.get(problemToken);
		
		return p == null ? null : p.toArray(new Problem[p.size()]);
	}

	private class ProblemNotification implements Runnable {
		
		public void run() {
			// union old model elements and new model elements with problems
			Set<ProblemToken> union = new HashSet<ProblemToken>(oldTokensWithProblems);
			union.addAll(token2problems.keySet());
			
			notify(union.iterator());
		}
		
		private void notify(Iterator<ProblemToken> i) {
			while (i.hasNext()) {
				ProblemToken me = i.next(); 
				me.fireProblemsChanged();
			}
		}
	}
}
